Public Class QLearning
    ' atributos

    Public Soluciones As ArrayList
    ' definimos el parametro de aprendizaje a utilizar en la funcion de valor 
    Dim parametroAprendizaje As Decimal



    Dim matrizCaminos(,,) As Integer
    'definimos la cantidad de epizodios durante los cuales correra el algoritmos de Q-learning
    Dim episodios As Integer
    Dim generador As New Random()
    'definimos el estado actual durante la ejecucion del algoritmo
    Dim estadoActual As Integer
    'definimos el estado objetivo para el cual se hara el calculo
    Dim estadoObjetivo As Integer
    Dim indiceEstadoObjetivo As Integer
    ' se define el atributo max que almacena la distancia maxima de la matriz de caminos
    Dim max As Integer
    Dim cantCiudades As Integer
    Dim barra As ProgressBar
    ' se define la penalizacion para un camino no existente
    Dim caminoNoExistente As Integer = -100
    Dim valorMaximo As Integer

    Public R(,) As Decimal
    Public Q(,) As Decimal
    'constructor de la clase Q-learning se definen todo los atributos 
    Public Sub New(ByVal parametroAprendizaje As Decimal, ByVal epizodios As Integer, ByVal estadoObjetivo As Integer, ByVal matrizCaminos As Array, ByVal barra As ProgressBar)
        Me.parametroAprendizaje = parametroAprendizaje
        Me.episodios = epizodios
        Me.estadoObjetivo = estadoObjetivo
        Me.indiceEstadoObjetivo = estadoObjetivo - 1
        Me.matrizCaminos = matrizCaminos
        Me.barra = barra
        Me.cantCiudades = Me.matrizCaminos.GetUpperBound(0) + 1
    End Sub
    ' se inicializan todas las variables a utilizar y se llama al metodo principal aprender para comenzar con
    'la ejecucion del algoritmo
    Public Sub Iniciar()
        Me.barra.Minimum = 0
        Me.barra.Maximum = Me.episodios + 1
        Me.barra.Value = 0
        Me.CalcularDistanciaMaxima()
        Me.inicializarQ()
        Me.inicializarR()
        Me.aprender()
        Me.generarResultados()
    End Sub
    'metodo que devuelve el maximo valor obtenido(considerando la matriz Q) entre las acciones posibles del siguiente estado
    Function buscarMaximoQ(ByVal estado As Integer) As Decimal
        Dim max As Decimal = 0
        For i As Integer = 0 To Me.Q.GetUpperBound(1)
            If R(estado, i) <> Me.caminoNoExistente Then
                If (Q(estado, i) > max) Then
                    max = Q(estado, i)
                End If
            End If
        Next
        Return max
    End Function
    'metodo que obtiene la distancia maxima de la matriz de caminos recorriendola por completo
    Sub CalcularDistanciaMaxima()
        Me.max = 0
        For i As Integer = 0 To Me.matrizCaminos.GetUpperBound(0)
            For j As Integer = 0 To Me.matrizCaminos.GetUpperBound(1)
                If Me.matrizCaminos(i, j, 0) > Me.max Then
                    Me.max = Me.matrizCaminos(i, j, 0)
                End If
            Next
        Next
    End Sub
    'metodo que pone a 0 la matriz Q
    Sub inicializarQ()
        ReDim Q(Me.matrizCaminos.GetUpperBound(0), Me.matrizCaminos.GetUpperBound(1))

        For i As Integer = 0 To Q.GetUpperBound(0)
            For j As Integer = 0 To Q.GetUpperBound(1)
                Me.Q(i, j) = 0
            Next
        Next
    End Sub
    'metodo que inicializa la matriz de recompenza R
    Sub inicializarR()
        Dim i As Integer
        Dim j As Integer
        ReDim R(Me.matrizCaminos.GetUpperBound(0), Me.matrizCaminos.GetUpperBound(1))

        For i = 0 To R.GetUpperBound(0)
            For j = 0 To R.GetUpperBound(1)
                If (i = Me.indiceEstadoObjetivo And i = j) Then
                    'si nos encontramos en la interseccion del nodo objetivo consigo mismo 
                    'recompensamos ese camino con vmax es decir la distancia maxima mas un adional que depende
                    ' de la cantidad de ciudades
                    R(i, j) = Me.max + (Me.cantCiudades / 2)
                Else
                    If Me.matrizCaminos(i, j, 0) = 0 Then
                        'si el camino no existe se lo penaliza con un valor negativo
                        R(i, j) = Me.caminoNoExistente
                    Else
                        'en otro caso en un camino existente la recompenza se obtiene restando a vmax(distancia maxima)
                        'la razon entre la distancia y el tipo de camino que se esta considerando
                        R(i, j) = Me.max - (Me.matrizCaminos(i, j, 0) / Me.matrizCaminos(i, j, 1))
                    End If
                End If
            Next
        Next
    End Sub

    'metodo principal donde se realiza el procedimiento de aprendizaje 
    Sub aprender()
        'definimos la accion seguiente que almacena la siguiente ciudad a la que se dirije dentro del algoritmo
        Dim accionSiguiente As Integer
        'variable que almacena el maximo valor obtenido(considerando la matriz Q) entre las acciones posibles del siguiente estado
        Dim maxQsigEstado As Single

        For i As Integer = 0 To Me.episodios
            Me.estadoActual = Me.generador.Next(0, cantCiudades)

            ' el bucle se repite hasta que no se de con la ciudad objetivo
            Do

                'Selecciono como accion siguiente solamente un camino valido
                'para ello se guarda en un array solamente las acciones posibles 
                
                Dim accionesPosibles As New ArrayList()
                For j As Integer = 0 To Me.matrizCaminos.GetUpperBound(0)
                    If Me.R(Me.estadoActual, j) <> Me.caminoNoExistente Then
                        accionesPosibles.Add(j)
                    End If
                Next
                'luego se calcula un random para acceder a dicho arrray
                'es decir un indice 
                Dim numAleatorio As Integer = Me.generador.Next(0, accionesPosibles.Count)

                accionSiguiente = accionesPosibles(numAleatorio)
                ' se obtiene el maximo valor posible para el siguiente estado
                maxQsigEstado = Me.buscarMaximoQ(accionSiguiente)
                Me.Q(Me.estadoActual, accionSiguiente) = R(Me.estadoActual, accionSiguiente) + (Me.parametroAprendizaje * maxQsigEstado)
                'Me.Q(Me.estadoActual, accionSiguiente) = Me.Q(Me.estadoActual, accionSiguiente) + Me.velocidadAprendizaje * (R(Me.estadoActual, accionSiguiente) + Me.parametroAprendizaje * maxQsigEstado - Me.Q(Me.estadoActual, accionSiguiente))
                Me.estadoActual = accionSiguiente


            Loop While (Me.estadoActual <> Me.estadoObjetivo - 1)
            Me.barra.Value += 1
        Next

        Me.generarResultados()
    End Sub

    Sub generarResultados()
        Me.Soluciones = New ArrayList()
        For i As Integer = 0 To Me.matrizCaminos.GetUpperBound(0)
            Dim camino As New ArrayList()
            If i <> Me.indiceEstadoObjetivo Then
                Dim actual As Integer = i
                Dim count As Integer = 1
                camino.Add(actual + 1)
                While actual <> Me.indiceEstadoObjetivo And count <= 15
                    Dim nuevo = Me.buscarMaximoIndiceQ(actual)
                    actual = nuevo
                    count += 1
                    camino.Add(nuevo + 1)
                End While
                Me.Soluciones.Add(camino)
            End If
        Next
    End Sub

    Function buscarMaximoIndiceQ(ByVal estado As Integer) As Single
        Dim max As Single = 0
        Dim index As Integer = 0
        For i As Integer = 0 To Me.Q.GetUpperBound(1)
            If R(estado, i) <> Me.caminoNoExistente Then
                If (Q(estado, i) > max) Then
                    max = Q(estado, i)
                    index = i
                End If
            End If
        Next
        Return index
    End Function
End Class